<template>
  <div class="login-page">
    <div class="visual-panel">
      <div ref="canvasContainer" class="canvas-container"></div>
      <div class="glow-overlay"></div>
      <div class="grid-overlay"></div>
    </div>
  <div class="form-panel">
      <div class="form-card">
        <h1 class="brand">欢迎回来 <span class="brand-glow"></span></h1>
        <!-- 角色选项卡 -->
        <div class="role-tabs">
          <button
            v-for="r in roles"
            :key="r.key"
            type="button"
            :class="['role-tab', 'role-tab--' + r.key, { 'role-tab--active': selectedRole === r.key }]"
            @click="handleRoleSelect(r.key)"
          >
            <span class="icon" aria-hidden="true">
              <!-- 图标使用 SVG 内发光滤镜，选中时仅在图标内部发光 -->
              <svg v-if="r.key === 'student'" width="16" height="16" viewBox="0 0 16 16">
                <defs>
                  <filter id="ig-student" x="-50%" y="-50%" width="200%" height="200%">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="1.5" result="blur" />
                    <feComposite in="blur" in2="SourceGraphic" operator="in" />
                  </filter>
                </defs>
                <g :filter="selectedRole === 'student' ? 'url(#ig-student)' : undefined">
                  <rect x="2" y="4" width="12" height="8" rx="1" fill="currentColor" opacity="0.22" />
                  <line x1="4" y1="6" x2="12" y2="6" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
                  <line x1="4" y1="9" x2="10" y2="9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
                </g>
              </svg>
              <svg v-else-if="r.key === 'teacher'" width="16" height="16" viewBox="0 0 16 16">
                <defs>
                  <filter id="ig-teacher" x="-50%" y="-50%" width="200%" height="200%">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="1.5" result="blur" />
                    <feComposite in="blur" in2="SourceGraphic" operator="in" />
                  </filter>
                </defs>
                <g :filter="selectedRole === 'teacher' ? 'url(#ig-teacher)' : undefined">
                  <polygon points="8,3 13,12 3,12" fill="currentColor" opacity="0.22" />
                  <circle cx="11.5" cy="11.5" r="1.3" fill="currentColor" />
                </g>
              </svg>
              <svg v-else width="16" height="16" viewBox="0 0 16 16">
                <defs>
                  <filter id="ig-admin" x="-50%" y="-50%" width="200%" height="200%">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="1.5" result="blur" />
                    <feComposite in="blur" in2="SourceGraphic" operator="in" />
                  </filter>
                </defs>
                <g :filter="selectedRole === 'admin' ? 'url(#ig-admin)' : undefined">
                  <polygon points="8,2 13,5 13,11 8,14 3,11 3,5" fill="currentColor" opacity="0.22" />
                  <polygon points="8,4.5 11.5,6.5 11.5,9.5 8,11.5 4.5,9.5 4.5,6.5" fill="currentColor" />
                </g>
              </svg>
            </span>
            <span class="role-label">{{ r.label }}</span>
          </button>
        </div>
        <a-form :model="loginForm" class="login-form">
          <div class="form-body" :class="{ 'switch-anim': animSwitching }">
          <a-form-item>
            <a-input
              v-model:value="loginForm.username"
              size="large"
              :placeholder="usernamePlaceholder"
            >
              <template #prefix>
                <UserOutlined />
              </template>
            </a-input>
          </a-form-item>
          <a-form-item>
            <a-input-password
              v-model:value="loginForm.password"
              size="large"
              placeholder="密码"
            >
              <template #prefix>
                <LockOutlined />
              </template>
          </a-input-password>
          </a-form-item>
          <div class="form-actions-top">
            <a-checkbox v-model:checked="loginForm.remember">记住我</a-checkbox>
            <a class="link-muted" href="javascript:void(0)">忘记密码？</a>
          </div>
          </div>
          <a-form-item>
            <a-button type="primary" size="large" block @click="handleLogin" :loading="loading">
              <transition name="fade" mode="out-in">
                <span :key="loginButtonText">{{ loginButtonText }}</span>
              </transition>
            </a-button>
          </a-form-item>
          <transition name="fade" mode="out-in">
            <div class="role-note" :key="roleNote">{{ roleNote }}</div>
          </transition>
        </a-form>
      </div>
    </div>
  </div>
  
</template>

<script setup lang="ts">
import { reactive, ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
import { authAPI } from '@/api/user'
import * as THREE from 'three'

const router = useRouter()
const loading = ref(false)

const loginForm = reactive({
  username: '',
  password: '',
  remember: true
})

// ===== 角色切换 =====
type Role = 'student' | 'teacher' | 'admin'
const selectedRole = ref<Role>('student')
const roles = [
  { key: 'student' as Role, label: '学生' },
  { key: 'teacher' as Role, label: '老师' },
  { key: 'admin' as Role, label: '管理员' }
]
const roleConfigs: Record<Role, { placeholder: string, note: string, button: string, accent: string }> = {
  student: { placeholder: '学号', note: '忘记学号？联系辅导员', button: '学生登录', accent: '#7b61ff' },
  teacher: { placeholder: '工号', note: '忘记工号？联系教务处', button: '教师登录', accent: '#8a75ff' },
  admin: { placeholder: '管理员账号', note: '仅内部人员使用', button: '管理员登录', accent: '#6a55ff' }
}
const usernamePlaceholder = computed(() => roleConfigs[selectedRole.value].placeholder)
const loginButtonText = computed(() => roleConfigs[selectedRole.value].button)
const roleNote = computed(() => roleConfigs[selectedRole.value].note)
const roleAccent = computed(() => roleConfigs[selectedRole.value].accent)
const animSwitching = ref(false)
const handleRoleSelect = (role: Role) => {
  if (selectedRole.value === role) return
  selectedRole.value = role
  animSwitching.value = true
  setTimeout(() => { animSwitching.value = false }, 300)
}

// ===== 3D 场景 =====
const canvasContainer = ref<HTMLDivElement | null>(null)
let renderer: THREE.WebGLRenderer | null = null
let scene: THREE.Scene | null = null
let camera: THREE.PerspectiveCamera | null = null
let animationId = 0
let resizeHandler: (() => void) | null = null
let mousemoveHandler: ((e: MouseEvent) => void) | null = null
let onMouseDownHandler: ((e: MouseEvent) => void) | null = null
let onMouseMoveDragHandler: ((e: MouseEvent) => void) | null = null
let onMouseUpHandler: (() => void) | null = null
let onMouseLeaveHandler: (() => void) | null = null
let onContextMenuHandler: ((e: MouseEvent) => void) | null = null
let onWheelHandler: ((e: WheelEvent) => void) | null = null
const baseCamPos = new THREE.Vector3(30, 26, 40)
const mouse = { x: 0, y: 0 }
// 交互控制参数
const yawMax = THREE.MathUtils.degToRad(15) // 水平 ±15°
const pitchMax = THREE.MathUtils.degToRad(10) // 俯仰 ±10°
const zMax = THREE.MathUtils.degToRad(5) // Z 轴限制 ±5°
const smoothingTau = 0.2 // 0.2 秒平滑时间常数
let zoom = 1.0 // 场景缩放（0.8~1.5）
let prevRot = new THREE.Vector3(0, 0, 0) // 上一帧旋转用于估算速度
// 自由旋转状态
let freeRotate = false
let dragging = false
let dragLast = { x: 0, y: 0 }
const dragSensitivity = 0.003 // 拖拽灵敏度（rad/px）
// 复位动画状态
let resetting = false
let resetStart = 0
const resetDuration = 1.5
let resetFrom = new THREE.Vector3(0, 0, 0)
// 目标旋转（期望值）
const targetRot = new THREE.Vector3(0, 0, 0)
let worldGroup: THREE.Group | null = null
let booksGroup: THREE.Group | null = null
let libraryMaterials: THREE.MeshStandardMaterial[] = []
let dataNodeMaterials: THREE.MeshStandardMaterial[] = []
let dashedLineMaterials: THREE.LineDashedMaterial[] = []
let bookStripeMaterials: THREE.MeshBasicMaterial[] = []
let microParticleMaterials: THREE.MeshStandardMaterial[] = []
// 外环流动光点、浮动方块、星空与短数据线端点
let ringDots: { mesh: THREE.Mesh, angle: number, speed: number, radius: number }[] = []
let floatingCubes: { mesh: THREE.Mesh, base: THREE.Vector3, amp: number, phase: number }[] = []
let starfieldGroup: THREE.Group | null = null
let shortDataLineEndpoints: THREE.Mesh[] = []
const clock = new THREE.Clock()

// 角色关联的灯光与粒子行为参数
let roleLightMultiplier = 1.0
let particleUpDriftSpeed = 0.02 // 学生默认缓慢上浮速度
let particleRotateZSpeed = 0.0   // 管理员增加的绕 Z 轴旋转速度
let ringDotSpeedFactor = 1.0     // 环上光点速度系数
let cubeFloatSpeedFactor = 1.0   // 浮动方块相位速度系数

// 数据线与书本连接锚点
const anchorMainBottom = new THREE.Vector3()
const anchorMainMiddle = new THREE.Vector3()
const anchorMainTop = new THREE.Vector3()
const anchorTowerBase = new THREE.Vector3()
const anchorLeftBook = new THREE.Vector3()
const anchorRightBook = new THREE.Vector3()

const initScene = () => {
  if (!canvasContainer.value) return
  const container = canvasContainer.value

  scene = new THREE.Scene()
  // 更柔和的浅色氛围（与页面浅紫灰到白渐变协调）
  scene.fog = new THREE.FogExp2(0xf5f2ff, 0.002)

  const width = container.clientWidth
  const height = container.clientHeight

  camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
  camera.position.set(baseCamPos.x, baseCamPos.y, baseCamPos.z)
  camera.lookAt(0, 0, 0)

  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
  renderer.setSize(width, height)
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
  renderer.shadowMap.enabled = true
  container.appendChild(renderer.domElement)

  // 灯光（柔和紫系）
  const ambient = new THREE.AmbientLight(0x9aa0ff, 0.75)
  scene.add(ambient)
  const dirLight = new THREE.DirectionalLight(0x8a5cff, 0.9)
  dirLight.position.set(40, 60, 30)
  dirLight.castShadow = true
  scene.add(dirLight)

  // 世界组：平台 + 图书馆 + 书本 + 数据线
  worldGroup = new THREE.Group()
  const platform = createPlatform()
  worldGroup.add(platform)
  const library = createLibrary()
  worldGroup.add(library)
  booksGroup = createBooks()
  worldGroup.add(booksGroup)
  const dataLines = createDataLines()
  worldGroup.add(dataLines)
  const auxBuildings = createAuxBuildings()
  worldGroup.add(auxBuildings)
  const shortLines = createShortDataLines()
  worldGroup.add(shortLines)
  scene.add(worldGroup)

  const stars = createStarfield()
  scene.add(stars)
  // 记录星空组以便联动
  starfieldGroup = stars

  const animate = () => {
    animationId = requestAnimationFrame(animate)
    const delta = clock.getDelta()
    const t = clock.elapsedTime

    // 建筑发光强度缓慢呼吸（控制亮度低于表单文字）
    const breathe = 1.0 + Math.sin(t * 0.8) * 0.05
    // 角度幅度带来的轻微发光增益（±0.1）
    const angleFactor = Math.min(1,
      (Math.abs(targetRot.x) / pitchMax + Math.abs(targetRot.y) / yawMax) / 2
    )
    const glowGain = THREE.MathUtils.clamp(angleFactor * 0.1, 0, 0.1)
    libraryMaterials.forEach((m) => { m.emissiveIntensity = (breathe + glowGain) * roleLightMultiplier })
    dataNodeMaterials.forEach((m) => { m.emissiveIntensity = (1.0 + Math.sin(t * 1.2) * 0.2 + glowGain) * roleLightMultiplier })
    microParticleMaterials.forEach((m) => { m.emissiveIntensity = (breathe + glowGain) * roleLightMultiplier })
    dashedLineMaterials.forEach((m) => { m.dashOffset = (m.dashOffset || 0) - delta * 0.6 })

    // 书本缓慢旋转（<= 0.02 rad/s）
    if (booksGroup) {
      booksGroup.children.forEach((child, idx) => {
        child.rotation.y += 0.012 * delta
        child.rotation.x += (idx % 2 === 0 ? 0.005 : -0.005) * delta
      })
    }

    // 交互：普通模式按鼠标位置，或自由拖拽模式
    if (worldGroup) {
      if (!freeRotate) {
        // 普通模式：基于鼠标位置的目标角度（Y ±15°, X ±10°）
        targetRot.y = mouse.x * yawMax
        targetRot.x = -mouse.y * pitchMax
      }

      // 复位动画（仅自由模式释放后触发）
      if (resetting) {
        const p = THREE.MathUtils.clamp((t - resetStart) / resetDuration, 0, 1)
        // easeOutCubic
        const e = 1 - Math.pow(1 - p, 3)
        targetRot.x = THREE.MathUtils.lerp(resetFrom.x, 0, e)
        targetRot.y = THREE.MathUtils.lerp(resetFrom.y, 0, e)
        targetRot.z = THREE.MathUtils.lerp(resetFrom.z, 0, e)
        if (p >= 1) {
          resetting = false
        }
      }

      // 平滑过渡（时间常数 0.2s）
      const alpha = 1 - Math.exp(-delta / smoothingTau)
      worldGroup.rotation.y = THREE.MathUtils.lerp(worldGroup.rotation.y, targetRot.y, alpha)
      worldGroup.rotation.x = THREE.MathUtils.lerp(worldGroup.rotation.x, targetRot.x, alpha)
      worldGroup.rotation.z = THREE.MathUtils.lerp(worldGroup.rotation.z, targetRot.z, alpha)
      // Z 轴限制 ±5°
      worldGroup.rotation.z = THREE.MathUtils.clamp(worldGroup.rotation.z, -zMax, zMax)

      // 场景缩放（0.8~1.5）
      if (zoom && worldGroup.scale) {
        worldGroup.scale.set(zoom, zoom, zoom)
      }
    }

    // 粒子与星空动态（联动 + 上浮 + 角色特效）
    // 背景星空轻微跟随，并添加缓慢上浮与（管理员）绕 Z 轴旋转
    if (starfieldGroup) {
      starfieldGroup.rotation.y = worldGroup ? worldGroup.rotation.y * 0.3 : 0
      starfieldGroup.rotation.x = worldGroup ? worldGroup.rotation.x * 0.2 : 0
      starfieldGroup.position.y += particleUpDriftSpeed * delta
      if (starfieldGroup.position.y > 4) starfieldGroup.position.y = 0
      starfieldGroup.rotation.z += particleRotateZSpeed * delta
    }

    // 环上流动光点：沿环匀速流动
    for (const d of ringDots) {
      d.angle += d.speed * ringDotSpeedFactor * delta
      d.mesh.position.set(Math.cos(d.angle) * d.radius, d.mesh.position.y, Math.sin(d.angle) * d.radius)
    }

    // 微型发光方块：上下轻微浮动（相位受角色影响）
    for (const fc of floatingCubes) {
      fc.phase += 0.8 * cubeFloatSpeedFactor * delta
      const y = fc.base.y + Math.sin(fc.phase) * fc.amp
      fc.mesh.position.y = y
    }

    // 保持相机稳定并指向中心
    if (camera) {
      camera.position.set(baseCamPos.x, baseCamPos.y, baseCamPos.z)
      camera.lookAt(0, 0, 0)
    }

    // 根据旋转速度添加轻微动态模糊（0~2px）
    if (renderer) {
      const dRx = (worldGroup?.rotation.x || 0) - prevRot.x
      const dRy = (worldGroup?.rotation.y || 0) - prevRot.y
      const dRz = (worldGroup?.rotation.z || 0) - prevRot.z
      const rotSpeed = (Math.abs(dRx) + Math.abs(dRy) + Math.abs(dRz)) / Math.max(delta, 1e-4)
      const blurPx = THREE.MathUtils.clamp(rotSpeed * 0.8, 0, 2)
      renderer.domElement.style.filter = `blur(${blurPx.toFixed(2)}px)`
      prevRot.set(worldGroup?.rotation.x || 0, worldGroup?.rotation.y || 0, worldGroup?.rotation.z || 0)
    }

    renderer && scene && camera && renderer.render(scene, camera)
  }
  animate()

  resizeHandler = () => {
    if (!renderer || !camera || !canvasContainer.value) return
    const w = canvasContainer.value.clientWidth
    const h = canvasContainer.value.clientHeight
    renderer.setSize(w, h)
    camera.aspect = w / h
    camera.updateProjectionMatrix()
  }
  window.addEventListener('resize', resizeHandler)

  mousemoveHandler = (e: MouseEvent) => {
    if (!canvasContainer.value) return
    const rect = canvasContainer.value.getBoundingClientRect()
    const nx = (e.clientX - rect.left) / rect.width
    const ny = (e.clientY - rect.top) / rect.height
    // 普通模式下按位置映射；自由模式则由拖拽处理
    if (!freeRotate) {
      mouse.x = (nx - 0.5) * 2
      mouse.y = (0.5 - ny) * 2
    }
  }
  container.addEventListener('mousemove', mousemoveHandler)

  // 自由旋转：右键或 Ctrl+左键
  onMouseDownHandler = (e: MouseEvent) => {
    if (!canvasContainer.value) return
    const isRight = e.button === 2
    const isCtrlLeft = e.button === 0 && e.ctrlKey
    if (isRight || isCtrlLeft) {
      freeRotate = true
      resetting = false
      dragging = true
      dragLast.x = e.clientX
      dragLast.y = e.clientY
    }
  }
  onMouseMoveDragHandler = (e: MouseEvent) => {
    if (!dragging || !freeRotate) return
    const dx = e.clientX - dragLast.x
    const dy = e.clientY - dragLast.y
    dragLast.x = e.clientX
    dragLast.y = e.clientY
    // 累积自由旋转，无角度限制（Z 轴单独限制）
    targetRot.y += dx * dragSensitivity
    targetRot.x += dy * dragSensitivity * -1
    // 限制 Z 轴
    targetRot.z = THREE.MathUtils.clamp(targetRot.z, -zMax, zMax)
  }
  onMouseUpHandler = () => {
    if (dragging) {
      dragging = false
      if (freeRotate) {
        // 结束自由模式并触发复位缓动
        freeRotate = false
        resetting = true
        resetStart = clock.elapsedTime
        resetFrom.set(targetRot.x, targetRot.y, targetRot.z)
      }
    }
  }
  onMouseLeaveHandler = () => onMouseUpHandler && onMouseUpHandler()
  onContextMenuHandler = (e: MouseEvent) => e.preventDefault()

  container.addEventListener('mousedown', onMouseDownHandler)
  window.addEventListener('mousemove', onMouseMoveDragHandler)
  window.addEventListener('mouseup', onMouseUpHandler)
  container.addEventListener('mouseleave', onMouseLeaveHandler)
  container.addEventListener('contextmenu', onContextMenuHandler)

  // 鼠标滚轮缩放（0.8~1.5）
  onWheelHandler = (e: WheelEvent) => {
    // 使用指数缩放，平滑手感
    const factor = Math.pow(1.0015, -e.deltaY)
    zoom = THREE.MathUtils.clamp(zoom * factor, 0.8, 1.5)
  }
  container.addEventListener('wheel', onWheelHandler, { passive: true })
}

// 根据角色切换调整发光强度与粒子细节（保持主色 #7b61ff 不变）
const applyRoleThemeByRole = (role: Role) => {
  // 灯光强度：学生 1.0（默认）、老师 1.1（略亮）、管理员 0.9（略暗）
  roleLightMultiplier = role === 'teacher' ? 1.1 : role === 'admin' ? 0.9 : 1.0
  // 粒子上浮速度：老师较快 10%，其他默认
  particleUpDriftSpeed = role === 'teacher' ? 0.022 : 0.02
  // 管理员添加轻微绕 Z 轴旋转效果（0.005 rad/s），其他为 0
  particleRotateZSpeed = role === 'admin' ? 0.005 : 0.0
  // 环点与方块的动态细微差异（保持协调）
  ringDotSpeedFactor = role === 'teacher' ? 1.05 : 1.0
  cubeFloatSpeedFactor = role === 'teacher' ? 1.1 : 1.0
}

watch(selectedRole, (r) => {
  applyRoleThemeByRole(r)
})

const createLibrary = () => {
  const group = new THREE.Group()

  // 主楼：三层分层，上窄下宽，每层边缘发光挑檐
  const levels = [
    { w: 18, h: 3.5, d: 12, y: 2.0 },
    { w: 14, h: 3.0, d: 10, y: 5.5 },
    { w: 11, h: 2.8, d: 8, y: 8.3 }
  ]
  levels.forEach((lvl) => {
    const geo = new THREE.BoxGeometry(lvl.w, lvl.h, lvl.d)
    const mat = new THREE.MeshStandardMaterial({
      color: 0xffffff,
      emissive: 0x7b61ff,
      emissiveIntensity: 1.1,
      roughness: 0.85,
      metalness: 0.0,
      transparent: true,
      opacity: 0.72
    })
    const mesh = new THREE.Mesh(geo, mat)
    mesh.position.set(0, lvl.y, 0)
    group.add(mesh)
    libraryMaterials.push(mat)

    // 发光挑檐（薄边框）
    const eave = new THREE.Mesh(
      new THREE.BoxGeometry(lvl.w + 0.6, 0.15, lvl.d + 0.6),
      new THREE.MeshStandardMaterial({
        color: 0xffffff,
        emissive: 0xf0eaff,
        emissiveIntensity: 1.2,
        roughness: 0.7,
        transparent: true,
        opacity: 0.85
      })
    )
    eave.position.set(0, lvl.y + lvl.h / 2 + 0.05, 0)
    group.add(eave)
    libraryMaterials.push(eave.material as THREE.MeshStandardMaterial)
  })

  // 底层正面玻璃幕墙纹理：横向发光线条（UV贴图）
  const facadeTex = makeStripeTexture('#e0d4ff')
  facadeTex.wrapS = facadeTex.wrapT = THREE.RepeatWrapping
  facadeTex.repeat.set(1, 1)
  const facade = new THREE.Mesh(
    new THREE.PlaneGeometry(levels[0].w * 0.9, levels[0].h * 0.9),
    new THREE.MeshStandardMaterial({
      color: 0xffffff,
      emissive: 0x7b61ff,
      emissiveIntensity: 0.9,
      map: facadeTex,
      transparent: true,
      opacity: 0.8
    })
  )
  facade.position.set(0, levels[0].y, levels[0].d / 2 + 0.01)
  group.add(facade)
  libraryMaterials.push(facade.material as THREE.MeshStandardMaterial)

  // 钟楼：矩形棱柱体 + 四棱锥尖顶
  const towerBodyGeo = new THREE.BoxGeometry(5, 10, 5)
  const towerMat = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    emissive: 0x7b61ff,
    emissiveIntensity: 1.1,
    roughness: 0.85,
    metalness: 0.0,
    transparent: true,
    opacity: 0.72
  })
  const tower = new THREE.Mesh(towerBodyGeo, towerMat)
  tower.position.set(-levels[0].w / 2 - 3, 7, 0)
  group.add(tower)
  libraryMaterials.push(towerMat)

  const pyramid = new THREE.Mesh(
    new THREE.ConeGeometry(3.2, 4.5, 4),
    new THREE.MeshStandardMaterial({
      color: 0xffffff,
      emissive: 0x7b61ff,
      emissiveIntensity: 1.3,
      roughness: 0.8,
      transparent: true,
      opacity: 0.75
    })
  )
  pyramid.position.set(tower.position.x, tower.position.y + 7.5, tower.position.z)
  pyramid.rotation.y = Math.PI / 4
  group.add(pyramid)
  libraryMaterials.push(pyramid.material as THREE.MeshStandardMaterial)

  const edgeGeo = new THREE.EdgesGeometry(new THREE.ConeGeometry(3.2, 4.5, 4))
  const edgeMat = new THREE.LineBasicMaterial({ color: 0xe0d4ff, transparent: true, opacity: 0.9 })
  const edges = new THREE.LineSegments(edgeGeo, edgeMat)
  edges.position.copy(pyramid.position)
  edges.rotation.copy(pyramid.rotation)
  group.add(edges)

  // 发光时钟面
  const clockFace = new THREE.Mesh(
    new THREE.CircleGeometry(1.6, 48),
    new THREE.MeshBasicMaterial({ color: 0x7b61ff, transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending })
  )
  clockFace.position.set(tower.position.x, tower.position.y + 2, tower.position.z + 2.6)
  group.add(clockFace)

  // 设置锚点
  anchorMainBottom.set(0, levels[0].y, levels[0].d / 2)
  anchorMainMiddle.set(0, levels[1].y, levels[1].d / 2)
  anchorMainTop.set(0, levels[2].y, levels[2].d / 2)
  anchorTowerBase.set(tower.position.x, tower.position.y - 5, tower.position.z)

  return group
}

// 附属建筑：两座低多边形科技楼，发光强度略低（0.9）
const createAuxBuildings = () => {
  const group = new THREE.Group()
  const makeBuilding = (pos: THREE.Vector3, size: THREE.Vector3) => {
    const body = new THREE.Mesh(
      new THREE.BoxGeometry(size.x, size.y, size.z),
      new THREE.MeshStandardMaterial({
        color: 0xffffff,
        emissive: 0x7b61ff,
        emissiveIntensity: 0.9,
        roughness: 0.85,
        metalness: 0.0,
        transparent: true,
        opacity: 0.72
      })
    )
    body.position.copy(pos)
    group.add(body)
    libraryMaterials.push(body.material as THREE.MeshStandardMaterial)
    const eave = new THREE.Mesh(
      new THREE.BoxGeometry(size.x + 0.6, 0.12, size.z + 0.6),
      new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xf0eaff, emissiveIntensity: 1.0, roughness: 0.7, transparent: true, opacity: 0.85 })
    )
    eave.position.set(pos.x, pos.y + size.y / 2 + 0.06, pos.z)
    group.add(eave)
    libraryMaterials.push(eave.material as THREE.MeshStandardMaterial)
  }
  // 分布在主楼四周 3×3×3 范围内的两个附属楼
  makeBuilding(new THREE.Vector3(10, 2.2, -8), new THREE.Vector3(6, 4.4, 4.2))
  makeBuilding(new THREE.Vector3(-12, 2.0, 9), new THREE.Vector3(5.2, 4.0, 3.8))
  return group
}

const createBooks = () => {
  const group = new THREE.Group()

  // 对称布局：左右两侧各两本（2开2合）
  const radius = 14
  const leftPos = new THREE.Vector3(-radius, 5.5, -6)
  const rightPos = new THREE.Vector3(radius, 5.5, 6)

  // 开本书（两片交叉45°），中间发光书脊
  const makeOpenBook = (pos: THREE.Vector3) => {
    const g = new THREE.Group()
    const leafGeo = new THREE.BoxGeometry(3, 0.15, 4)
    const leafMat = new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xe0d4ff, emissiveIntensity: 0.8, roughness: 0.7 })
    const leaf1 = new THREE.Mesh(leafGeo, leafMat)
    const leaf2 = new THREE.Mesh(leafGeo, leafMat)
    leaf1.rotation.y = THREE.MathUtils.degToRad(45)
    leaf2.rotation.y = THREE.MathUtils.degToRad(-45)
    g.add(leaf1)
    g.add(leaf2)
    const spine = new THREE.Mesh(
      new THREE.BoxGeometry(0.2, 0.3, 4.2),
      new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xe0d4ff, emissiveIntensity: 1.0, roughness: 0.6 })
    )
    g.add(spine)
    g.position.copy(pos)
    group.add(g)
    libraryMaterials.push(leafMat)
    libraryMaterials.push(spine.material as THREE.MeshStandardMaterial)
    return g
  }

  // 合本书（带横向发光线条）
  const makeClosedBook = (pos: THREE.Vector3) => {
    const g = new THREE.Group()
    const body = new THREE.Mesh(
      new THREE.BoxGeometry(3, 0.5, 4),
      new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xe0d4ff, emissiveIntensity: 0.7, roughness: 0.7 })
    )
    g.add(body)
    // 横向发光线条（贴在表面）
    for (let i = -1; i <= 1; i++) {
      const stripe = new THREE.Mesh(
        new THREE.PlaneGeometry(2.8, 0.05),
        new THREE.MeshBasicMaterial({ color: 0xe0d4ff, transparent: true, opacity: 0.9, blending: THREE.AdditiveBlending })
      )
      stripe.position.set(0, 0.26, i * 0.6)
      stripe.rotation.x = -Math.PI / 2
      g.add(stripe)
      bookStripeMaterials.push(stripe.material as THREE.MeshBasicMaterial)
    }
    g.position.copy(pos)
    group.add(g)
    libraryMaterials.push(body.material as THREE.MeshStandardMaterial)
    return g
  }

  const leftOpen = makeOpenBook(leftPos.clone())
  const leftClosed = makeClosedBook(leftPos.clone().add(new THREE.Vector3(2.5, 0, 2)))
  const rightOpen = makeOpenBook(rightPos.clone())
  const rightClosed = makeClosedBook(rightPos.clone().add(new THREE.Vector3(-2.5, 0, -2)))

  anchorLeftBook.copy(leftOpen.position)
  anchorRightBook.copy(rightClosed.position)

  return group
}

const createDataLines = () => {
  const group = new THREE.Group()

  const makeLine = (points: THREE.Vector3[]) => {
    const curve = new THREE.CatmullRomCurve3(points)
    const geo = new THREE.BufferGeometry().setFromPoints(curve.getPoints(50))
    const mat = new THREE.LineDashedMaterial({ color: 0xe0d4ff, dashSize: 0.4, gapSize: 0.25 })
    const line = new THREE.Line(geo, mat)
    line.computeLineDistances()
    group.add(line)
    dashedLineMaterials.push(mat)
    return line
  }

  const makeNode = (pos: THREE.Vector3) => {
    const node = new THREE.Mesh(
      new THREE.SphereGeometry(0.35, 24, 24),
      new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0x7b61ff, emissiveIntensity: 1.0, roughness: 0.7 })
    )
    node.position.copy(pos)
    group.add(node)
    dataNodeMaterials.push(node.material as THREE.MeshStandardMaterial)
  }

  // 1) 主楼底层 → 塔楼底部
  makeLine([
    anchorMainBottom.clone(),
    new THREE.Vector3((anchorMainBottom.x + anchorTowerBase.x) / 2, anchorMainBottom.y + 1.5, -3),
    anchorTowerBase.clone()
  ])
  makeNode(new THREE.Vector3((anchorMainBottom.x + anchorTowerBase.x) / 2, anchorMainBottom.y + 1.5, -3))
  makeNode(anchorTowerBase.clone())

  // 2) 主楼中层 → 右书本
  makeLine([
    anchorMainMiddle.clone(),
    new THREE.Vector3(6, anchorMainMiddle.y + 2, 2),
    new THREE.Vector3(10, anchorMainMiddle.y + 2, 4),
    anchorRightBook.clone()
  ])
  makeNode(new THREE.Vector3(6, anchorMainMiddle.y + 2, 2))
  makeNode(new THREE.Vector3(10, anchorMainMiddle.y + 2, 4))
  makeNode(anchorRightBook.clone())

  // 3) 主楼顶层 → 左书本
  makeLine([
    anchorMainTop.clone(),
    new THREE.Vector3(-6, anchorMainTop.y + 2, -2),
    anchorLeftBook.clone()
  ])
  makeNode(new THREE.Vector3(-6, anchorMainTop.y + 2, -2))
  makeNode(anchorLeftBook.clone())

  return group
}

function makeStripeTexture(hex: string) {
  const size = 256
  const canvas = document.createElement('canvas')
  canvas.width = size
  canvas.height = size
  const ctx = canvas.getContext('2d')!
  ctx.clearRect(0, 0, size, size)
  ctx.strokeStyle = hex
  ctx.globalAlpha = 0.8
  for (let y = 10; y < size; y += 18) {
    ctx.beginPath()
    ctx.moveTo(0, y)
    ctx.lineTo(size, y)
    ctx.lineWidth = 2
    ctx.shadowBlur = 6
    ctx.shadowColor = hex
    ctx.stroke()
  }
  const tex = new THREE.CanvasTexture(canvas)
  tex.needsUpdate = true
  return tex
}

const createPlatform = () => {
  const group = new THREE.Group()

  const gradTex = makeRadialGradientTexture('#7b61ff', '#e0d4ff', '#6a4fd8')
  const disk = new THREE.Mesh(
    new THREE.CircleGeometry(22, 64),
    new THREE.MeshBasicMaterial({ map: gradTex, transparent: true, opacity: 0.95 })
  )
  disk.rotation.x = -Math.PI / 2
  group.add(disk)

  const border = new THREE.Mesh(
    new THREE.RingGeometry(21.5, 22.5, 64),
    new THREE.MeshBasicMaterial({ color: 0xf0eaff, transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending })
  )
  border.rotation.x = -Math.PI / 2
  group.add(border)

  for (let r = 8; r <= 18; r += 5) {
    const ring = new THREE.Mesh(
      new THREE.RingGeometry(r - 0.2, r + 0.2, 64),
      new THREE.MeshBasicMaterial({ color: 0xf0eaff, transparent: true, opacity: 0.7, blending: THREE.AdditiveBlending })
    )
    ring.rotation.x = -Math.PI / 2
    group.add(ring)
  }

  const particles: THREE.Mesh[] = []
  for (let i = 0; i < 6; i++) {
    const angle = (i / 6) * Math.PI * 2
    const px = Math.cos(angle) * 22
    const pz = Math.sin(angle) * 22
    const p = new THREE.Mesh(
      new THREE.SphereGeometry(0.25, 16, 16),
      new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xe0d4ff, emissiveIntensity: 0.9, roughness: 0.7, transparent: true, opacity: 0.95 })
    )
    p.position.set(px, 0.02, pz)
    group.add(p)
    particles.push(p)
    microParticleMaterials.push(p.material as THREE.MeshStandardMaterial)
  }
  for (let i = 0; i < particles.length; i++) {
    const a = particles[i]
    const b = particles[(i + 1) % particles.length]
    const geo = new THREE.BufferGeometry().setFromPoints([a.position.clone(), b.position.clone()])
    const mat = new THREE.LineBasicMaterial({ color: 0xf0eaff, transparent: true, opacity: 0.5 })
    const line = new THREE.Line(geo, mat)
    group.add(line)
  }
  // 外扩数据环（宽度约 1），带流动光点动画
  const ringRadius = 26
  const ring = new THREE.Mesh(
    new THREE.TorusGeometry(ringRadius, 0.5, 24, 128),
    new THREE.MeshBasicMaterial({ color: 0xf0eaff, transparent: true, opacity: 0.5 })
  )
  ring.rotation.x = -Math.PI / 2
  group.add(ring)
  // 环上流动光点
  for (let i = 0; i < 12; i++) {
    const dot = new THREE.Mesh(
      new THREE.SphereGeometry(0.18, 16, 16),
      new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0x7b61ff, emissiveIntensity: 0.9, roughness: 0.6 })
    )
    const angle = (i / 12) * Math.PI * 2
    dot.position.set(Math.cos(angle) * ringRadius, 0.02, Math.sin(angle) * ringRadius)
    group.add(dot)
    ringDots.push({ mesh: dot, angle, speed: 0.6 + Math.random() * 0.4, radius: ringRadius })
    microParticleMaterials.push(dot.material as THREE.MeshStandardMaterial)
  }

  // 六个微型发光方块（随机上下浮动）
  for (let i = 0; i < 6; i++) {
    const ang = (i / 6) * Math.PI * 2 + Math.random() * 0.4
    const r = 20 + Math.random() * 6
    const cube = new THREE.Mesh(
      new THREE.BoxGeometry(0.5, 0.5, 0.5),
      new THREE.MeshStandardMaterial({ color: 0xe0d4ff, emissive: 0x7b61ff, emissiveIntensity: 0.8, roughness: 0.7 })
    )
    const base = new THREE.Vector3(Math.cos(ang) * r, 0.5 + Math.random() * 0.4, Math.sin(ang) * r)
    cube.position.copy(base)
    group.add(cube)
    floatingCubes.push({ mesh: cube, base, amp: 0.25 + Math.random() * 0.15, phase: Math.random() * Math.PI * 2 })
    microParticleMaterials.push(cube.material as THREE.MeshStandardMaterial)
  }

  return group
}

function makeRadialGradientTexture(center: string, mid: string, edge: string) {
  const size = 512
  const canvas = document.createElement('canvas')
  canvas.width = size
  canvas.height = size
  const ctx = canvas.getContext('2d')!
  const grd = ctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2)
  grd.addColorStop(0.0, center)
  grd.addColorStop(0.5, mid)
  grd.addColorStop(1.0, edge)
  ctx.fillStyle = grd
  ctx.fillRect(0, 0, size, size)
  const tex = new THREE.CanvasTexture(canvas)
  tex.wrapS = tex.wrapT = THREE.ClampToEdgeWrapping
  tex.needsUpdate = true
  return tex
}

// 星空粒子层（密度适中，粒子大小 0.1~0.3，#7b61ff）
const createStarfield = () => {
  const starsGroup = new THREE.Group()
  const makeStars = (count: number, size: number) => {
    const geo = new THREE.BufferGeometry()
    const positions = new Float32Array(count * 3)
    for (let i = 0; i < count; i++) {
      const idx = i * 3
      positions[idx + 0] = (Math.random() - 0.5) * 120
      positions[idx + 1] = Math.random() * 60 + 10
      positions[idx + 2] = - (Math.random() * 80 + 30)
    }
    geo.setAttribute('position', new THREE.BufferAttribute(positions, 3))
    const mat = new THREE.PointsMaterial({ color: 0x7b61ff, size, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false })
    const points = new THREE.Points(geo, mat)
    starsGroup.add(points)
  }
  makeStars(120, 0.1)
  makeStars(80, 0.2)
  makeStars(50, 0.3)
  starfieldGroup = starsGroup
  return starsGroup
}

// 新增短数据流线（5 条），线条端点带微小粒子动态
const createShortDataLines = () => {
  const group = new THREE.Group()
  const sources = [anchorMainBottom.clone(), anchorMainMiddle.clone(), anchorMainTop.clone(), anchorLeftBook.clone(), anchorRightBook.clone()]
  for (let i = 0; i < 5; i++) {
    const s = sources[i]
    const e = new THREE.Vector3((Math.random() - 0.5) * 40, 0.5 + Math.random() * 1.0, (Math.random() - 0.5) * 40)
    const curve = new THREE.CatmullRomCurve3([s, new THREE.Vector3((s.x + e.x) / 2, s.y + 1.5, (s.z + e.z) / 2), e])
    const geo = new THREE.BufferGeometry().setFromPoints(curve.getPoints(30))
    const mat = new THREE.LineDashedMaterial({ color: 0xe0d4ff, dashSize: 0.35, gapSize: 0.25 })
    const line = new THREE.Line(geo, mat)
    line.computeLineDistances()
    group.add(line)
    dashedLineMaterials.push(mat)
    const endNode = new THREE.Mesh(
      new THREE.SphereGeometry(0.2, 16, 16),
      new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0x7b61ff, emissiveIntensity: 0.9, roughness: 0.6, transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending })
    )
    endNode.position.copy(e)
    group.add(endNode)
    shortDataLineEndpoints.push(endNode)
    microParticleMaterials.push(endNode.material as THREE.MeshStandardMaterial)
  }
  return group
}

onMounted(() => {
  initScene()
})

onBeforeUnmount(() => {
  if (animationId) cancelAnimationFrame(animationId)
  if (resizeHandler) window.removeEventListener('resize', resizeHandler)
  if (mousemoveHandler && canvasContainer.value) {
    canvasContainer.value.removeEventListener('mousemove', mousemoveHandler as any)
  }
  // 解绑新增事件监听器
  if (onMouseDownHandler && canvasContainer.value) {
    canvasContainer.value.removeEventListener('mousedown', onMouseDownHandler as any)
  }
  if (onMouseMoveDragHandler) {
    window.removeEventListener('mousemove', onMouseMoveDragHandler as any)
  }
  if (onMouseUpHandler) {
    window.removeEventListener('mouseup', onMouseUpHandler as any)
  }
  if (onMouseLeaveHandler && canvasContainer.value) {
    canvasContainer.value.removeEventListener('mouseleave', onMouseLeaveHandler as any)
  }
  if (onContextMenuHandler && canvasContainer.value) {
    canvasContainer.value.removeEventListener('contextmenu', onContextMenuHandler as any)
  }
  if (onWheelHandler && canvasContainer.value) {
    canvasContainer.value.removeEventListener('wheel', onWheelHandler as any)
  }
  if (renderer) {
    renderer.dispose()
  }
  scene = null
  camera = null
})

// ===== 登录逻辑 =====
const handleLogin = async () => {
  if (!loginForm.username || !loginForm.password) {
    message.warning('请输入用户名和密码')
    return
  }
  
  loading.value = true
  try {
    const res = await authAPI.login({
      username: loginForm.username,
      password: loginForm.password
    })
    const storage = loginForm.remember ? localStorage : sessionStorage
    storage.setItem('token', res.data.data.token)
    storage.setItem('user', JSON.stringify(res.data.data.user))
    
    message.success('登录成功')
    router.push('/dashboard')
  } catch (error: any) {
    message.error(error.response?.data?.message || '登录失败,请检查用户名和密码')
  } finally {
    loading.value = false
  }
}
</script>

<style scoped>
/* 角色选项卡样式 */
.role-tabs {
  display: flex;
  gap: 8px;
  margin: 12px 0 8px;
}
.role-tab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 12px;
  border-radius: 8px;
  color: #6a4fd8;
  background: transparent;
  border: 1px solid rgba(123, 97, 255, 0.18);
  position: relative;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.role-tab:hover {
  background: #f0eaff;
}
.role-tab--active {
  background: #7b61ff;
  color: #ffffff;
  border-color: rgba(123, 97, 255, 0.35);
}
/* 选中下边框改为渐变色，按角色区分 */
.role-tab--student.role-tab--active::after,
.role-tab--teacher.role-tab--active::after,
.role-tab--admin.role-tab--active::after {
  content: '';
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: -1px;
  height: 2px;
  border-radius: 2px;
}
.role-tab--student.role-tab--active::after { background: linear-gradient(90deg, #7b61ff, #e0d4ff); }
.role-tab--teacher.role-tab--active::after { background: linear-gradient(90deg, #8a75ff, #e0d4ff); }
.role-tab--admin.role-tab--active::after { background: linear-gradient(90deg, #6a55ff, #e0d4ff); }
.role-tab .icon {
  display: inline-flex;
  margin-right: 8px;
}
.role-tab .role-label {
  font-weight: 600;
}

/* 表单过渡动画容器 */
.form-body {
  transition: transform 0.3s ease;
  will-change: transform;
}
.form-body.switch-anim {
  transform: scaleY(0.95);
}

/* 输入框边框颜色过渡：角色切换时从角色色淡入默认色 */
.form-body :deep(.ant-input-affix-wrapper) {
  transition: border-color 0.3s ease;
  border-color: #f0eaff;
}
.form-body.switch-anim :deep(.ant-input-affix-wrapper) {
  border-color: var(--role-accent) !important;
}

/* 选中时图标内发光颜色（配合 SVG 内滤镜） */
.role-tab--student.role-tab--active .icon { color: #7b61ff; }
.role-tab--teacher.role-tab--active .icon { color: #8a75ff; }
.role-tab--admin.role-tab--active .icon { color: #6a55ff; }

/* 按钮与辅助文案淡入淡出 */
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }

/* 底部角色提示文案 */
.role-note {
  margin-top: -4px;
  color: #6a4fd8;
  font-size: 13px;
  text-align: center;
}
</style>

<style scoped>
.login-page {
  width: 100vw;
  height: 100vh;
  display: flex;
  position: relative;
  overflow: hidden;
  background: linear-gradient(to bottom, #f5f2ff, #ffffff);
  animation: page-fade-in 600ms ease-out both;
}

/* 移除深色叠层，保留简洁浅色背景氛围 */

.visual-panel {
  flex: 1.15;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}

.canvas-container {
  width: 100%;
  height: 100%;
}

.glow-overlay {
  position: absolute;
  inset: 0;
  background: radial-gradient( ellipse at 50% 50%, rgba(72,100,255,0.15), rgba(0,0,0,0) 55% );
  mix-blend-mode: screen;
  pointer-events: none;
}

.grid-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(0deg, rgba(123,97,255,0.08) 0px, rgba(123,97,255,0.08) 1px, rgba(0,0,0,0) 1px, rgba(0,0,0,0) 20px),
    repeating-linear-gradient(90deg, rgba(123,97,255,0.08) 0px, rgba(123,97,255,0.08) 1px, rgba(0,0,0,0) 1px, rgba(0,0,0,0) 20px);
}

.form-panel {
  flex: 0.85;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 2;
}

.form-card {
  width: 420px;
  padding: 28px 28px 22px;
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(12px);
  border-radius: 16px;
  position: relative;
  animation: card-fade-up 680ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

.form-card::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 16px;
  padding: 1px;
  background: linear-gradient(135deg, rgba(123,97,255,0.6), rgba(123,97,255,0.0));
  -webkit-mask: linear-gradient(#fff, #fff) content-box, linear-gradient(#fff, #fff);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.form-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 16px;
  box-shadow: 0 0 0 1px #f0eaff inset;
  pointer-events: none;
}

.form-card:hover::before { filter: brightness(1.2); }

.brand {
  margin: 0 0 24px;
  color: #cdd6f4;
  font-weight: 600;
  font-size: 22px;
  letter-spacing: 0.4px;
  text-align: center;
  position: relative;
}

.brand-glow {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: radial-gradient(circle, #f7a65a 0, #7aa2f7 60%, transparent 70%);
  box-shadow: 0 0 24px #f7a65a;
  margin-left: 8px;
  vertical-align: middle;
}

.login-form { width: 100%; }

.form-actions-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 4px 2px 12px;
}

.link-muted {
  color: rgba(205, 214, 244, 0.8);
  text-decoration: none;
  position: relative;
}
.link-muted:hover { color: #7b61ff; }
.link-muted::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  height: 2px;
  width: 0;
  background: #7b61ff;
  transition: width 220ms ease;
}
.link-muted:hover::after { width: 100%; }

:deep(.ant-input), :deep(.ant-input-affix-wrapper) {
  background-color: rgba(255, 255, 255, 0.35);
  border-color: rgba(123, 97, 255, 0.35);
  color: #1c1f2b;
  transition: border-color 200ms ease, box-shadow 200ms ease, transform 200ms ease;
}

:deep(.ant-input::placeholder) { color: #d0c8ff; }

:deep(.ant-input:focus), :deep(.ant-input-affix-wrapper-focused) {
  border-color: #7b61ff !important;
  box-shadow: 0 0 8px #7b61ff !important;
  transform: translateZ(0) scale(1.01);
}

:deep(.ant-btn-primary) {
  height: 40px;
  border-radius: 10px;
  background: linear-gradient(#6a4fd8, #5a45b9);
  box-shadow: 0 8px 30px rgba(110, 138, 255, 0.25);
  border: none;
  transition: transform 100ms ease, box-shadow 200ms ease;
}

:deep(.ant-btn-primary:hover) { box-shadow: 0 0 15px #7b61ff; }
:deep(.ant-btn-primary:active) { transform: scale(0.98); }

@keyframes page-fade-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes card-fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}

</style>
